Skip to content

feat(mvux): advanced mocking & previews — spec 013 + implementation (WIP) - #3165

Merged
dr1rrb merged 19 commits into
mainfrom
dev/devid/spec-013-mvux-mocking
Sep 4, 2026
Merged

feat(mvux): advanced mocking & previews — spec 013 + implementation (WIP)#3165
dr1rrb merged 19 commits into
mainfrom
dev/devid/spec-013-mvux-mocking

Conversation

@dr1rrb

@dr1rrb dr1rrb commented Aug 25, 2026

Copy link
Copy Markdown
Member

Summary

Draft PR tracking the "Advanced MVUX mocking & previews" work.

Lands Spec 013 – MVUX Mocking & Previews (design under
specs/013-mvux-mocking-previews/) and will carry the implementation on top.
Kept as draft until the implementation is in.

Scope

  • Spec 013 design (spec.md, architecture.md, implementation.md, history.md)
  • Implementation — 3-tier design:
    • Tier 1: non-generic MessageEntry CLR type in Core
    • Tiers 2/3: strongly-typed, mock at the Model-feed cache level, derived feeds preserved
    • Generated mocking on the external side
  • Tests
  • Docs

Related

Closes #3150
Depends on #3149 (per issue, should land first)

@CLAassistant

CLAassistant commented Aug 25, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

dr1rrb added 14 commits August 25, 2026 20:52
Restored state = end of discussion (lost commits 8d589d9 -> 292fb5f ->
2618def -> cd4c9ad), merged from local recovery notes + David's VS Code
copies (his architecture.md was the most recent, post-2618def).

- spec.md: 3 tiers, real VM + real Model with null-injected services,
  derived feeds survive via Model-feed cache swap anchor, external
  mocking generator (consumer test project), D1-D9 decision log,
  open question: context-wide scope / ambient MockingService.Enable()
- architecture.md: seams, swap anchor, MVUX gen (analysis + dependency
  attributes + hidden typed hooks) vs Mocking gen (metadata-driven
  {Model}Mock/Create/SetModel), tier-1 authorable plain-CLR MessageEntry
  (not a DependencyObject, not observable) with first-class custom axes,
  natural feed evolution contract, XAML examples
- implementation.md: package split, attribute shapes, hidden hooks,
  typed vocabulary, diagnostics FEED3201-3203/MOCK0001, P0 canaries
  (derived-swap gate, null-inject, identity matrix, context spike),
  test plan, docs plan
- history.md: full version/decision chronology of the spec discussion
- spec.md: audience/deliverable split, derived-feeds-survive flow,
  three-tier layering, generator responsibility split, end-to-end
  sequence, tier-1 evolution sequence; risks and decision log as tables;
  status header cleaned for team/CEO review (history stays in history.md)
- architecture.md: swap-anchor mermaid in section 1, end-to-end ASCII
  flow replaced by a sequence diagram in section 5
…he swap fail hard

Records the decision to drop the dedicated static flag in favour of a per-context bit, and
to reuse the hot-reload reflection driver with strict failure instead of best effort.
Adds a per-context bit inherited at creation and reads it where the wrap is decided, so only
contexts created under an activation scope wrap their feeds and a live application pays
nothing.
Classifies every feed member as service dependent, derived or independent, and instruments
the constructors so an eagerly dereferenced service is reported. The metadata is what the
consumer generator reads.
Commands have no hot-swap state, so a dedicated seam reassigns the command property after
construction. Construction itself needs no seam: the public constructors plus the ambient
scope are enough.
Adds the activation scope, the swap engine and the typed feed vocabulary used by the
generated mocks.
The consumer generator reads the app metadata and emits the mock record, the Create
factories and the SetModel facade.

StateImpl always implements IHotSwapState, so the previous type test never rejected a
non-wrapped feed and the swap silently did nothing. Added CanHotSwap and made the engine
throw when a mocked feed is not swappable.
Adds Value, Empty, Undefined, Loading, Error and Refreshing for both scalar feeds and list
feeds, built on pinned messages and strongly typed end to end.
…cabulary

The generated record exposes Empty with every input pinned to its empty state, Create()
builds from it, and commands can be overridden through the view-model seam.
Logs what landed for the tier 2 and 3 implementation and what is left out of scope.
The mocked feed vocabulary already exists in Uno.HotTesting.Reactive, so the duplicate
assembly is removed and everything lives there: FeedMock and ListFeedMock are reused,
CommandMock is added, and the swap engine moves into MockingService as strongly typed
helpers. The consumer generator becomes Uno.HotTesting.Reactive.Generator and emits raw
string literals.

The MVUX instrumentation is now emitted by default, with an assembly level opt-out.
…ourceContext

The ambient activation state is a mocking concern, not a Core one. MockingService now owns
the AsyncLocal and registers a probe; SourceContext only keeps the per-instance
IsMockingActive bit. In a live application the probe is never registered, so the bit stays
false and the cost is zero.
@dr1rrb
dr1rrb force-pushed the dev/devid/spec-013-mvux-mocking branch from ff6f0d5 to 7893286 Compare August 25, 2026 20:57
Comment thread src/Uno.Extensions.Reactive/Utils/Disposables/Disposable.cs
Comment thread src/Uno.HotTesting.Reactive.Generator/FeedsMockGenerator.cs
Comment thread src/Uno.HotTesting.Reactive.Generator/FeedsMockGenerator.cs Fixed
Comment thread src/Uno.HotTesting.Reactive.Generator/FeedsMockGenerator.cs Fixed
dr1rrb added 3 commits August 25, 2026 21:10
Extended the reactive testing reference with the generated tier 2 and 3 layer, and added a
named catalog sample together with its test. The generated factory class is now named
{Vm}Mock and Empty moved onto the record.
… Create

Review feedback: the generated {Vm}Mock is now partial so applications can extend it with
named catalogs, SetModel is renamed to SetMock, Create only takes the mock record,
{Model}Mock.Empty stays on the record so it composes with `with`, and Create opens the
MockingService.Enable() scope itself so user code never has to.

Command mocking is deferred to a future version: the consumer generator no longer emits a
command member nor the __Mock_SetCommand wiring.
Applied: combined the nested if in ViewModelGenTool_3.Mocking, projected
ctor.DeclaringSyntaxReferences, SimpleNameSyntax and the accesses with Select, and
filtered the nested types and the references explicitly in FeedsMockGenerator.

Declined, each answered in its thread: the readonly suggestion on Disposable._onDispose
is a false positive (the field is a ref argument of Interlocked.Exchange), the == false
simplification targets a bool? whose three-state semantics are intentional, and the Where
suggestion filters a computed value.
@dr1rrb
dr1rrb force-pushed the dev/devid/spec-013-mvux-mocking branch from 5191d49 to 18f0bf2 Compare September 2, 2026 18:56
@dr1rrb

dr1rrb commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Code-quality review addressed in 18f0bf2.

Applied (7): nested-if combined in ViewModelGenTool_3.Mocking; explicit .Where(HasFeedDep) on nested types and .Select(...).OfType<IAssemblySymbol>() on references in FeedsMockGenerator; .Select(...) projections for ctor.DeclaringSyntaxReferences (2 sites), SimpleNameSyntax to symbol, and access to receiver.

Declined (3), each answered in its thread: the readonly suggestion on Disposable._onDispose is a false positive (the field is a ref argument of Interlocked.Exchange, CS0192); the == false simplification targets a bool? whose three-state semantics are intentional; the Where suggestion on the generator loop filters a computed value and would only add a tuple allocation.

Tests: Given_MockingActivation 4/4, Given_MockingRuntime 4/4, Given_GeneratedMock 4/4, Tests.Generator 80/80, Uno.HotTesting.Reactive.Tests 22/22.

The repository enables GenerateDocumentationFile together with TreatWarningsAsErrors,
so a public member without an XML comment fails the Release build even though a local
Debug build stays green. Added /// <inheritdoc /> on FeedsMockGenerator.Initialize and
Execute, the same way FeedsGenerator already does.

Also fixed MD032, MD012, MD022 and MD031 on the four spec 013 documents.
Everything pushed to GitHub must be in English. The spec 013 history document was
written in French during the design discussions, which failed the CI spell-checking
validation (333 unknown words). spec.md, architecture.md and the reference doc were
already clean.

- history.md fully translated to English, keeping every SHA, type name, file path and
  decision id unchanged.
- implementation.md: replaced three coined words the dictionary rejects (bikesheddable,
  Authorable, mockability) with plain English equivalents.

Verified with the exact CI commands: cspell over 236 markdown files reports 0 issue, and
markdownlint reports no violation.
@dr1rrb
dr1rrb force-pushed the dev/devid/spec-013-mvux-mocking branch from 18f0bf2 to de78a92 Compare September 2, 2026 20:50
@dr1rrb

dr1rrb commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

CI failures fixed (head is now de78a92). Four independent causes:

  1. CS1591 — the one that broke the builds. src/Directory.Build.props enables GenerateDocumentationFile together with TreatWarningsAsErrors, so a public member without an XML comment fails the Release build (invisible in a local Debug build). Added /// <inheritdoc /> on FeedsMockGenerator.Initialize/Execute, the same way FeedsGenerator already does. This alone accounted for four red checks: Packages, Build Samples (Windows and WebAssembly) and UI Tests WebAssembly. Verified with a Release build of the three affected projects.
  2. Markdown validation. MD032/MD012/MD022/MD031 on the four specs/013 documents, fixed with markdownlint --fix; the reference documentation page was already clean.
  3. Conventional commits. Five commit subjects used non-existent types (spec(013) three times, docs+sample(mocking), and docs(spec 013) whose scope contained a space). Rewritten to docs(spec-013) and docs(mocking).
  4. Spell checking. The spec 013 history document had been written in French (333 unknown words) while everything pushed to GitHub must be in English; it is now fully translated, keeping every SHA, type name and decision id. Three coined words in implementation.md were also reworded.

Both linters were re-run with the exact CI commands over the whole repository: cspell reports 0 issue across 236 markdown files, and markdownlint reports no violation.

@dr1rrb
dr1rrb force-pushed the dev/devid/spec-013-mvux-mocking branch 2 times, most recently from 4e897d3 to 4a3e1b3 Compare September 3, 2026 14:18
@dr1rrb
dr1rrb marked this pull request as ready for review September 3, 2026 16:26
@mergify

mergify Bot commented Sep 4, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@dr1rrb
dr1rrb merged commit 2e3b759 into main Sep 4, 2026
27 of 28 checks passed
@dr1rrb
dr1rrb deleted the dev/devid/spec-013-mvux-mocking branch September 4, 2026 13:35
@dr1rrb

dr1rrb commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

@Mergifyio backport servicing/7.4

@mergify

mergify Bot commented Sep 4, 2026

Copy link
Copy Markdown

backport servicing/7.4

✅ Backports have been created

Details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

♥️ [MVUX] Advanced mocking

3 participants